home *** CD-ROM | disk | FTP | other *** search
/ Chip 2003 October / Chip Ekim 2003.iso / prog / code / contr / setup.exe / Disk1 / data1.cab / Configuration_En / Commands / PMModules.js < prev    next >
Encoding:
JavaScript  |  2003-07-18  |  45.2 KB  |  1,554 lines

  1. //=========================================================================================================
  2. //
  3. // Copyright 2002, 2003 Macromedia, Inc. All rights reserved.
  4. //
  5. // Feature: Paste Fix
  6. // Author:  JDH
  7. // Module:  PMModules.js
  8. // Purpose:    The modules for the Paste Fix pipeline.
  9. // Updates:
  10. //    5/17/02 - Started file control
  11. //  5/30/02 - Added performance upgrade enhancements
  12. //  5/31/02 - Added extensive comments
  13. //  6/3/02 - Added support for STRONG and EM, as well as more comments
  14. //
  15. //=========================================================================================================
  16.  
  17.  
  18. // The main purpose of the the Paste Fix system is to provide an adaptable filter to take vendor specific
  19. // HTML and massage it into HTML that is reasonable, readable, effecient, and very importantly, editable in
  20. // the host MM application.  To that end we apply a series of scanners and filter to the HTML in a specific
  21. // series. The scanners analyze the document, looking for signatures, so that the filters can act more
  22. // specifically.  The filters both remove and alter the HTML to match needs of the application, and the current
  23. // security settings.
  24.  
  25. // Filters come in two main flavors; cleaning filters, and conversion filters.  Cleaning filters simply remove
  26. // tags, attributes, styles, directives, and content that is not editable, or required by, the host application.
  27. // Conversion filters analyze the tags and attempt to recreate the same effect as the vendor specific tag with
  28. // standard HTML.  For example, if a TD tag references a class, we look at the tag and the class, and create a
  29. // series of font, bold, italic, etc. tags to recreate the effect of the class. This makes the document more easily
  30. // editable by standard HTML editors.
  31.  
  32. // The key point here is that the whole system is designed to remove what it doesn't understand.  So to add new
  33. // items that the system will understand you will most likely have to add support in several places.  For example,
  34. // to add support for borders around tables you will need to make sure the style filter (if you implement the
  35. // borders as styles) does not filter out the styles you have just added.  To do this you add style specifications
  36. // to some of the global tables below.
  37.  
  38. // If your intent is to add a new security mode then you will most likely have to touch the run method of
  39. // every filter to either bring it into the stream when your mode is ON, or remove it from the stream when
  40. // your mode is on.  In addition you may have to create new sets of tag, attribute or style filters like those
  41. // in the globals section. The ETO mode is an example here.  In the basic case the 'NORMAL' tag retention
  42. // settings are used, and in ETO mode teh ETO tag retention set is used.
  43.  
  44.  
  45. //=========================================================================================================
  46. // Globals
  47. //=========================================================================================================
  48.  
  49. // Listed below are sets of tag, attributes and styles that are either to be remove or retained through
  50. // the operation of the filter.  Each associative array, or nested set of associative array, is used to
  51. // configure a filter.
  52.  
  53. // NOTE: Associative arrays were used here to speed up access in the filters.  The value of '1' is used
  54. // simply as a placeholder and has no semantic value in any case.
  55.  
  56.  
  57. // The list of tags that will be retained in the normal operating mode of the filter
  58.  
  59. var RETAIN_TAGS_NORMAL = {
  60.     html: 1, body: 1, table: 1, td: 1, tr: 1, thead: 1,
  61.     span: 1, b: 1, i: 1, p: 1, u: 1,
  62.     ul: 1, li: 1, ol: 1,
  63.     h1: 1, h2: 1, h3: 1, h4: 1, h5: 1, h6: 1,
  64.     img: 1, a: 1,
  65.     hr: 1, br: 1
  66. };
  67.  
  68. // The list of tags that will be retained in the ETO mode of the filter
  69.  
  70. var RETAIN_TAGS_ETO = {
  71.     html: 1, body: 1, p: 1
  72. };
  73.  
  74. // Some tags will want to be retained through the midsection of the filter, but removed at the end.
  75. // Those tags are listed here.
  76.  
  77. var PRS_REMOVE_TAGS = {
  78.     thead: 1
  79. };
  80.  
  81. // These tags are to removed if they have no attributes.
  82.  
  83. var PRS_REMOVE_IF_NO_ATTRIBUTES = {
  84.     span: 1, font: 1, a: 1
  85. };
  86.  
  87. // Tags listed here are supposed to be removed if they contain no interior.
  88.  
  89. var PRS_REMOVE_IF_EMPTY = {
  90.     p: 1
  91. };
  92.  
  93. // Tags listed here are supposed to be renamed if they contain no interior.
  94.  
  95. var PRS_RENAME_IF_EMPTY = {
  96.     div: "br"
  97. };
  98.  
  99. // The tags that the Decomposer should not inspect
  100.  
  101. var DC_IGNORE_TAGS = { table: 1, img: 1, col: 1 };
  102.  
  103.  
  104. // These are the attributes to be retained for each type of tag.  The primary key is the tag name
  105. // (coerced to lower case), then within that entry there should be an associative array where the
  106. // keys are the attributes to be retained.
  107.  
  108. var RUA_TAG_SPECIFIC_ATTRIBUTES = {
  109.     span: { style: 1 },
  110.     font: { style: 1, face: 1, size: 1 },
  111.     ul: { style: 1 },
  112.     ol: { style: 1 },
  113.     td: { 'width': 1, 'bgcolor': 1, 'class': 1, 'align': 1, 'valign': 1, 'colspan': 1, 'rowspan': 1 },
  114.     th: { 'width': 1, 'bgcolor': 1, 'class': 1, 'align': 1, 'valign': 1, 'scope': 1 },
  115.     p: { align: 1, 'class': 1 },
  116.     h1: { align: 1 },
  117.     h2: { align: 1 },
  118.     h3: { align: 1 },
  119.     h4: { align: 1 },
  120.     h5: { align: 1 },
  121.     h6: { align: 1 },
  122.     img: { src: 1, height: 1, width: 1, alt: 1 },
  123.     a: { href: 1, name: 1 },
  124.     br: { style: 1 },
  125.     div: { align: 1 }
  126. };
  127.  
  128.  
  129. // Within the style attribute of any tag you can specify what values are to be retained through the
  130. // filter.  If your attribute isn't listed here then it will be stripped by the filter, so you will
  131. // want to add an entry here for the tag and for the specific style attribute.
  132.  
  133. var RUA_TAG_SPECIFIC_STYLES = {
  134.     font: { "font-family": 1, "font-size": 1, "color": 1 },
  135.     span: { "font-family": 1, "font-size": 1, "color": 1 },
  136.     br: { "mso-break-type": 1 }
  137. };
  138.  
  139.  
  140.  
  141. //=========================================================================================================
  142. // Filter modules
  143. //=========================================================================================================
  144.  
  145.  
  146. //---------------------------------------------------------------------------------------------------------
  147. // ParseMetaTags
  148. //---------------------------------------------------------------------------------------------------------
  149.  
  150. // The ParseMetaTags module finds all of the meta tags and puts that information
  151. // into the context.
  152.  
  153. function ParseMetaTags() { }
  154.  
  155. // The module API
  156.  
  157. ParseMetaTags.prototype.run = ParseMetaTags_run;
  158. ParseMetaTags.prototype.getPhase = ParseMetaTags_getPhase;
  159.  
  160. function ParseMetaTags_run( context )
  161. {
  162.     // Ignore non-HTML content
  163.     
  164.     if ( context.getContentType() != CONTENT_TYPE_HTML )
  165.         return true;
  166.  
  167.     context.debugInformation( "ParseMetaTags", ">> run" );
  168.  
  169.     // Build the meta tag scanner
  170.  
  171.     var metaParser = new GetMetaTagsScanner();
  172.     var metaTags = metaParser.scan( context.getClipText() );
  173.  
  174.     // Set the context with the current value of the meta tags
  175.  
  176.     for( var key in metaTags )
  177.         context.setMeta( key, metaTags[ key ] );
  178.  
  179.     context.debugInformation( "ParseMetaTags", "<< run" );
  180.  
  181.     return true;
  182. }
  183.  
  184. function ParseMetaTags_getPhase() { return PHASE_ANALYZE; }
  185.  
  186.  
  187. //---------------------------------------------------------------------------------------------------------
  188. // FixupMSGarbage
  189. //---------------------------------------------------------------------------------------------------------
  190.  
  191. // FixupMSGarbage fixes various problems with the HTML generated by the MSWordProcessingApplication
  192. // and the MSSpreadsheetApplication.
  193.  
  194. function FixupMSGarbage() { }
  195.  
  196. // The module API
  197.  
  198. FixupMSGarbage.prototype.run = FixupMSGarbage_run;
  199. FixupMSGarbage.prototype.getPhase = FixupMSGarbage_getPhase;
  200.  
  201. function FixupMSGarbage_run( context )
  202. {
  203.     // Ignore non-HTML content
  204.     
  205.     if ( context.getContentType() != CONTENT_TYPE_HTML )
  206.         return true;
  207.  
  208.     context.debugInformation( "FixupMSGarbage", ">> run" );
  209.  
  210.     // Get the document
  211.     
  212.     var html = context.getClipText();
  213.  
  214.     // Fix the bad MS quotes model.
  215.  
  216.     var quotesScanner = new FixQuotesScanner();
  217.     html = quotesScanner.scan( html, context );
  218.  
  219.     // Turn MS specific list items into reasonable plain text with paragraph
  220.     // markup
  221.  
  222.     var listScanner = new ParseSupportListsScanner();
  223.     html = listScanner.scan( html, context );
  224.  
  225.     // Remove everything from the HTML except the section within the fragment
  226.  
  227.     var findClippingScanner = new FindClippingScanner();
  228.     html = "<html><body>" + findClippingScanner.scan( html, context ) + "</body></html>";
  229.  
  230.     var parasScanner = new FixSupportEmptyParasScanner( { img: 1 } );
  231.     html = parasScanner.scan( html, context );
  232.     
  233.     // Remove any conditionals, but retain images within the conditionals
  234.  
  235.     var conditionalScanner = new RemoveConditionalsScanner( { img: 1 } );
  236.     html = conditionalScanner.scan( html, context );
  237.  
  238.     if ( context.getOriginApplication() == "word" || context.getOriginApplication() == "excel" )
  239.     {
  240.         var remHiddenSpansScanner = new RemoveHiddenSpansScanner( );
  241.         html = remHiddenSpansScanner.scan( html, context );
  242.     }
  243.  
  244.     // In the case of excel or word, dump the contents of comment div tags
  245.  
  246.     if ( context.getOriginApplication() == "word" || context.getOriginApplication() == "excel" )
  247.     {
  248.         var divsScanner = new RemoveCommentDIVScanner( );
  249.         html = divsScanner.scan( html, context );
  250.     }
  251.  
  252.     // In the case of an MSSpreadSheetApplication add the table element into the HTML
  253.  
  254.     var fixTable = false;
  255.  
  256.     if ( html.match( /\<\!\-\-(\s*)StartFragment(\s*)\-\-\>(\s*)<tr/i ) )
  257.         fixTable = true;
  258.  
  259.     if ( context.getOriginApplication() == "excel" )
  260.     {
  261.         if ( ! html.match( /\<\!\-\-(\s*)StartFragment(\s*)\-\-\>(\s*)<div/i ) )
  262.             fixTable = true;
  263.     }
  264.  
  265.     if ( fixTable )
  266.     {
  267.         html = html.replace( /\<\!\-\-(\s*)StartFragment(\s*)\-\-\>/, "<!--StartFragment--><table>" );
  268.         html = html.replace( /\<\!\-\-(\s*)EndFragment(\s*)\-\-\>/, "</table><!--EndFragment-->" );
  269.     }
  270.  
  271.     context.setClipText( html );
  272.  
  273.     context.debugInformation( "FixupMSGarbage", "<< run" );
  274.  
  275.     return true;
  276. }
  277.  
  278. function FixupMSGarbage_getPhase() { return PHASE_FIXUP; }
  279.  
  280.  
  281. //---------------------------------------------------------------------------------------------------------
  282. // IdentifyMSApplications
  283. //---------------------------------------------------------------------------------------------------------
  284.  
  285. // IdentifyMSApplications looks at the meta tag information and parses out where the information
  286. // came from.
  287.  
  288. function IdentifyMSApplications() { }
  289.  
  290. // The module API
  291.  
  292. IdentifyMSApplications.prototype.run = IdentifyMSApplications_run;
  293. IdentifyMSApplications.prototype.getPhase = IdentifyMSApplications_getPhase;
  294.  
  295. function IdentifyMSApplications_run( context )
  296. {
  297.     context.debugInformation( "IdentifyMSApplications", ">> run" );
  298.  
  299.     // Look for the generator meta tag
  300.  
  301.     var name = context.getMeta( "generator" );
  302.     if ( name != null )
  303.     {
  304.         // Store the full application name
  305.  
  306.         context.setOriginApplicationFull( name );
  307.  
  308.         // Parse MSWordProcessingApplication signatures
  309.  
  310.         if ( name.match( /microsoft word/i ) )
  311.         {
  312.             context.setOriginApplication( "word" );
  313.             context.setOriginApplicationVersion( name.split( " " )[ 2 ] );
  314.         }
  315.  
  316.         // Parse MSSpreadSheetApplication signatures
  317.  
  318.         if ( name.match( /microsoft excel/i ) )
  319.         {
  320.             context.setOriginApplication( "excel" );
  321.             context.setOriginApplicationVersion( name.split( " " )[ 2 ] );
  322.         }
  323.  
  324.         context.debugInformation( "IDMS", "Origin Application: " + context.getOriginApplication() );
  325.         context.debugInformation( "IDMS", "Origin Application Version: " + context.getOriginApplicationVersion() );
  326.     }
  327.  
  328.     context.debugInformation( "IdentifyMSApplications", "<< run" );
  329.  
  330.     return true;
  331. }
  332.  
  333. function IdentifyMSApplications_getPhase() { return PHASE_IDENTIFICATION; }
  334.  
  335.  
  336.  
  337. //---------------------------------------------------------------------------------------------------------
  338. // RetainStructure
  339. //---------------------------------------------------------------------------------------------------------
  340.  
  341. // RetainStructure removes any tags from the HTML stream that are not required by the host
  342. // application.
  343.  
  344. function RetainStructure() { }
  345.  
  346. // The module API
  347.  
  348. RetainStructure.prototype.run = RetainStructure_run;
  349. RetainStructure.prototype.getPhase = RetainStructure_getPhase;
  350.  
  351. function RetainStructure_run( context )
  352. {
  353.     // Ignore non-HTML content
  354.  
  355.     if ( context.getContentType() != CONTENT_TYPE_HTML )
  356.         return true;
  357.  
  358.     // Ignore this filter if we are not running in Contribute
  359.  
  360.     if ( ! context.settingDefined( SETTINGS_CONTRIBUTE ) )
  361.         return true;
  362.  
  363.     context.debugInformation( "RetainStructure", ">> run" );
  364.  
  365.     // Store a reference to the appropriate tag set
  366.  
  367.     var retainSet = RETAIN_TAGS_NORMAL;
  368.  
  369.     if ( context.settingDefined( SETTINGS_ETO ) )
  370.     {
  371.         context.debugInformation( "RetainStructure", "Using ETO tag set" );
  372.         retainSet = RETAIN_TAGS_ETO;
  373.     }
  374.  
  375.     // Add in BR and DIV if we are not looking at Word or Excel
  376.  
  377.     if ( context.getOriginApplication() != "excel" &&
  378.          context.getOriginApplication() != "word" )
  379.     {
  380.         retainSet[ "br" ] = true;
  381.         retainSet[ "div" ] = true;
  382.     }
  383.  
  384.     // Get the clipboard
  385.  
  386.     var html = context.getClipText();
  387.  
  388.     // Return the remove tag scanner with our set of tags
  389.     // to retain
  390.  
  391.     var remTags = new RemoveTagsScanner( retainSet, context );
  392.     html = remTags.scan( html, context );
  393.  
  394.     // alert if we're in ETO and what the user's about to paste is emtpy.
  395.     if (context.settingDefined( SETTINGS_ETO ) && html == "<html><body><!--StartFragment--><!--EndFragment--></body></html>")
  396.     {
  397.         alertString = dw.loadString ("pasteManager/etoWarning");
  398.           alert (alertString);
  399.     }
  400.  
  401.     // Send the output back to the context
  402.  
  403.     context.setClipText( html );
  404.  
  405.     context.debugInformation( "RetainStructure", "<< run" );
  406.  
  407.     return true;
  408. }
  409.  
  410. function RetainStructure_getPhase() { return PHASE_CONFORM_STRUCTURE; }
  411.  
  412.  
  413.  
  414. //---------------------------------------------------------------------------------------------------------
  415. // RemoveUnsupportedAttributes
  416. //---------------------------------------------------------------------------------------------------------
  417.  
  418. // RemoveUnsupportedAttributes removes unwanted attributes and styles from the HTML
  419. // stream. 
  420.  
  421. function RemoveUnsupportedAttributes() { }
  422.  
  423. // The module API
  424.  
  425. RemoveUnsupportedAttributes.prototype.run = RemoveUnsupportedAttributes_run;
  426. RemoveUnsupportedAttributes.prototype.getPhase = RemoveUnsupportedAttributes_getPhase;
  427.  
  428. function RemoveUnsupportedAttributes_run( context )
  429. {
  430.     // Ignore the content if it's not HTML
  431.  
  432.     if ( context.getContentType() != CONTENT_TYPE_HTML )
  433.         return true;
  434.  
  435.     // Ignore this filter if we are not running in Contribute
  436.  
  437.     if ( ! context.settingDefined( SETTINGS_CONTRIBUTE ) )
  438.         return true;
  439.  
  440.     context.debugInformation( "RemoveUnsupportedAttributes", ">> run" );
  441.  
  442.     // Get the clipboard text context
  443.  
  444.     var html = context.getClipText();
  445.  
  446.     // Remove unsupported tags and attributes
  447.  
  448.     var attributeRemover = new RemoveAttributesScanner( RUA_TAG_SPECIFIC_ATTRIBUTES, RUA_TAG_SPECIFIC_STYLES );
  449.     html = attributeRemover.scan( html, context );
  450.  
  451.     // Send the text back to the clipboard
  452.  
  453.     context.setClipText( html );
  454.  
  455.     context.debugInformation( "RemoveUnsupportedAttributes", "<< run" );
  456.  
  457.     return true;
  458. }
  459.  
  460. function RemoveUnsupportedAttributes_getPhase() { return PHASE_CONFORM_OTHER; }
  461.  
  462.  
  463.  
  464.  
  465. //---------------------------------------------------------------------------------------------------------
  466. // ConvertEmptyDivs
  467. //---------------------------------------------------------------------------------------------------------
  468.  
  469. // ConvertEmptyDivs converts <DIV></DIV> groupings to <BR />. 
  470.  
  471. function ConvertEmptyDivs() { }
  472.  
  473. // The module API
  474.  
  475. ConvertEmptyDivs.prototype.run = ConvertEmptyDivs_run;
  476. ConvertEmptyDivs.prototype.getPhase = ConvertEmptyDivs_getPhase;
  477.  
  478. function ConvertEmptyDivs_run( context )
  479. {
  480.     // Ignore the content if it's not HTML
  481.  
  482.     if ( context.getContentType() != CONTENT_TYPE_HTML )
  483.         return true;
  484.  
  485.     // Ignore this filter if we are not running in Contribute
  486.  
  487.     if ( ! context.settingDefined( SETTINGS_CONTRIBUTE ) )
  488.         return true;
  489.  
  490.     // Ignore this filter if we are converting word or excel
  491.  
  492.     if ( context.getOriginApplication() == "excel" ||
  493.          context.getOriginApplication() == "word" )
  494.         return true;
  495.  
  496.     context.debugInformation( "ConvertEmptyDivs", ">> run" );
  497.  
  498.     // Get the clipboard text context
  499.  
  500.     var html = context.getClipText();
  501.  
  502.     // Run the attribute remove scanner with our set of attributes and styles
  503.     // to retain
  504.  
  505.     var attributeRemover = new RemoveAttributesScanner( RUA_TAG_SPECIFIC_ATTRIBUTES, RUA_TAG_SPECIFIC_STYLES );
  506.     html = attributeRemover.scan( html, context );
  507.  
  508.     // Run the empty tag renaming scanner
  509.  
  510.     var emptyTagRenamer = new RenameEmptyTagsScanner( PRS_RENAME_IF_EMPTY );
  511.     html = emptyTagRenamer.scan( html, context );
  512.  
  513.     // Send the text back to the clipboard
  514.  
  515.     context.setClipText( html );
  516.  
  517.     context.debugInformation( "ConvertEmptyDivs", "<< run" );
  518.  
  519.     return true;
  520. }
  521.  
  522. function ConvertEmptyDivs_getPhase() { return PHASE_CONFORM_OTHER; }
  523.  
  524.  
  525. //---------------------------------------------------------------------------------------------------------
  526. // RemoveCSSClasses
  527. //---------------------------------------------------------------------------------------------------------
  528.  
  529. // RemoveCSSClasses removes class attributes from all tags if we are runing without CSS
  530. // or running in ETO mode.
  531.  
  532. function RemoveCSSClasses() { }
  533.  
  534. // The module API
  535.  
  536. RemoveCSSClasses.prototype.run = RemoveCSSClasses_run;
  537. RemoveCSSClasses.prototype.getPhase = RemoveCSSClasses_getPhase;
  538.  
  539. function RemoveCSSClasses_run( context )
  540. {
  541.     // Ignore non-HTML content
  542.  
  543.     if ( context.getContentType() != CONTENT_TYPE_HTML )
  544.         return true;
  545.  
  546.     // Ignore this filter if we are not in Contribute
  547.  
  548.     if ( ! context.settingDefined( SETTINGS_CONTRIBUTE ) )
  549.         return true;
  550.  
  551.     // Ignore this filter if we are using CSS
  552.  
  553.     if ( ! context.settingDefined( SETTINGS_ETO ) )
  554.         return true;
  555.  
  556.     context.debugInformation( "RemoveCSSClasses", ">> run" );
  557.  
  558.     // Get the clipboard HTML
  559.  
  560.     var html = context.getClipText();
  561.  
  562.     // Run the scanner that removes specific attributes.  In this case, remove the
  563.     // class attributes.
  564.  
  565.     var attributeRemover = new RemoveOnlyTheseAttributesScanner( { 'class': 1 } );
  566.     html = attributeRemover.scan( html, context );
  567.  
  568.     // Put back the HTML
  569.  
  570.     context.setClipText( html );
  571.  
  572.     context.debugInformation( "RemoveCSSClasses", "<< run" );
  573.  
  574.     return true;
  575. }
  576.  
  577. function RemoveCSSClasses_getPhase() { return PHASE_FIXUP; }
  578.  
  579.  
  580. //---------------------------------------------------------------------------------------------------------
  581. // RemoveParsingRequiredStructuralTags
  582. //---------------------------------------------------------------------------------------------------------
  583.  
  584. // RemoveParsingRequiredStructuralTags removes any tags from the HTML stream that were required
  585. // during the parsing (like THEAD) but are not required by the host application.
  586.  
  587. function RemoveParsingRequiredStructuralTags() {}
  588.  
  589. RemoveParsingRequiredStructuralTags.prototype = new StructureScanner();
  590.  
  591. // The module API
  592.  
  593. RemoveParsingRequiredStructuralTags.prototype.run = RemoveParsingRequiredStructuralTags_run;
  594. RemoveParsingRequiredStructuralTags.prototype.getPhase = RemoveParsingRequiredStructuralTags_getPhase;
  595.  
  596. // The structure scanner override methods
  597.  
  598. RemoveParsingRequiredStructuralTags.prototype.createTag = RemoveParsingRequiredStructuralTags_createTag;
  599. RemoveParsingRequiredStructuralTags.prototype.finalizeTag = RemoveParsingRequiredStructuralTags_finalizeTag;
  600.  
  601. function RemoveParsingRequiredStructuralTags_run( context )
  602. {
  603.     // Ignore non-HTML content
  604.  
  605.     if ( context.getContentType() != CONTENT_TYPE_HTML )
  606.         return true;
  607.  
  608.     // Ignore this filter if we are not in Contribute
  609.  
  610.     if ( ! context.settingDefined( SETTINGS_CONTRIBUTE ) )
  611.         return true;
  612.  
  613.     context.debugInformation( "RemoveParsingRequiredStructuralTags", ">> run" );
  614.  
  615.     // Get the clipboard HTML
  616.  
  617.     var html = context.getClipText();
  618.  
  619.     // Remove specific tags from the HTML
  620.  
  621.     var tagHash = PRS_REMOVE_TAGS;
  622.  
  623.     // Remove font and span tags if we can't alter the font
  624.  
  625.     if ( context.settingDefined( SETTINGS_NO_CSS ) )
  626.     {
  627.         tagHash[ "font" ] = true;
  628.         tagHash[ "span" ] = true;
  629.     }
  630.  
  631.     var remTags = new RemoveOnlyTheseTagsScanner( tagHash );
  632.     html = remTags.scan( html, context );
  633.  
  634.     // Now use ourselves to remove specific tags that have specific problems,
  635.     // like no content or no attributes.
  636.  
  637.     html = this.scan( html );
  638.  
  639.     // Set the clipboard HTML
  640.  
  641.     context.setClipText( html );
  642.  
  643.     context.debugInformation( "RemoveParsingRequiredStructuralTags", "<< run" );
  644.  
  645.     return true;
  646. }
  647.  
  648. function RemoveParsingRequiredStructuralTags_getPhase() { return PHASE_FINALIZE; }
  649.  
  650. function RemoveParsingRequiredStructuralTags_createTag( tag, attributes, closed )
  651. {
  652.     // If you look at this method and the method below you will think to yourself,
  653.     // "Why not merge the two?"  Well, the requirements are different.  In the case
  654.     // of no attributes you just want to remove the tag, not the interior of the tag.
  655.     // For example, this:
  656.     //
  657.     //    <p><font>My Text</font></p>
  658.     //
  659.     // Should become:
  660.     //
  661.     //    <p>My Text</p>
  662.     //
  663.     // In the case of removing empty tags we are looking to do this:
  664.     //
  665.     //    <p>Some text</p><p></p><p>Some more text</p>
  666.     //
  667.     // Should become:
  668.     //
  669.     //    <p>Some text</p><p>Some more text</p>
  670.     //
  671.     // But you can only do that if you know what the child HTML is, and you only
  672.     // know that in the finalize phase.
  673.  
  674.     // If this tag is on the check list then run the check
  675.  
  676.     if ( PRS_REMOVE_IF_NO_ATTRIBUTES[ tag.toLowerCase() ] > 0 )
  677.     {
  678.         // Create a 0 or 1 count of attributes
  679.         var count = 0;
  680.         for( var key in attributes )
  681.         {
  682.             count = 1;
  683.             break;
  684.         }
  685.  
  686.         // If the count is zero then return a blank tag text output
  687.         if ( count == 0 )
  688.             return { postfix: "", prefix: "" };
  689.     }
  690.  
  691.     // Otherwise, let the base class handle it
  692.  
  693.     return StructureScanner_createTag( tag, attributes, closed );
  694. }
  695.  
  696. function RemoveParsingRequiredStructuralTags_finalizeTag( tag, attributes, closed, childHTML )
  697. {
  698.     // If this tag is in the check list the check it
  699.  
  700.     if ( PRS_REMOVE_IF_EMPTY[ tag ] > 0 )
  701.     {
  702.         // If there is no interior, then kill the tag.
  703.         
  704.         if ( Utils_StripWhitespace( childHTML ).length < 1 )
  705.             return false;
  706.     }
  707.  
  708.     return true;
  709. }
  710.  
  711.  
  712.  
  713.  
  714. //---------------------------------------------------------------------------------------------------------
  715. // DecomposeClasses
  716. //---------------------------------------------------------------------------------------------------------
  717.  
  718. // DecomposeClasses turns CSS style information into inline HTML formatting.  For example, the
  719. // HTML:
  720. //
  721. //   <html><head><style><!-- MsoNormal { font-family: Arial } --></style>
  722. //   <body><p class=MsoNormal>Hello</p></body></html>
  723. //
  724. // Should become:
  725. //
  726. //   <html><head><style><!-- MsoNormal { font-family: Arial } --></style>
  727. //   <body><p><font face="Arial">Hello</p></body></html>
  728. //
  729. // This allows Contribute users to edit the HTML using Contribute.
  730.  
  731. function DecomposeClasses() {}
  732.  
  733. DecomposeClasses.prototype = new StructureScanner ();
  734.  
  735. // Methods to make use a filter component
  736.  
  737. DecomposeClasses.prototype.run = DecomposeClasses_run;
  738. DecomposeClasses.prototype.getPhase = DecomposeClasses_getPhase;
  739.  
  740. // Local methods
  741.  
  742. DecomposeClasses.prototype.parseClass = DecomposeClasses_parseClass;
  743. DecomposeClasses.prototype.buildNewTags = DecomposeClasses_buildNewTags;
  744. DecomposeClasses.prototype.buildTagSpecifications = DecomposeClasses_buildTagSpecifications;
  745. DecomposeClasses.prototype.shouldUseTargetClass = DecomposeClasses_shouldUseTargetClass;
  746.  
  747. // StructureScanner overrides
  748.  
  749. DecomposeClasses.prototype.startTag = DecomposeClasses_startTag;
  750. DecomposeClasses.prototype.endTag = DecomposeClasses_endTag;
  751. DecomposeClasses.prototype.createTag = DecomposeClasses_createTag;
  752.  
  753. function DecomposeClasses_run( context )
  754. {
  755.     // Initialize the decomposition class and the other member variables
  756.  
  757.     this._cache = {};
  758.     this._isHeader = false;
  759.  
  760.     // Ignore this if the content is not HTML
  761.  
  762.     if ( context.getContentType() != CONTENT_TYPE_HTML )
  763.         return true;
  764.  
  765.     // Ignore this filter if we are not in Contribute
  766.  
  767.     if ( ! context.settingDefined( SETTINGS_CONTRIBUTE ) )
  768.         return true;
  769.  
  770.     // Ignore this filter if we are not in ETO mode
  771.  
  772.     if ( context.settingDefined( SETTINGS_ETO ) )
  773.         return true;
  774.  
  775.     context.debugInformation( "DecomposeClasses", ">> run" );
  776.  
  777.     this._context = context;
  778.  
  779.     // Decide on whether we are building fonts or spans
  780.  
  781.     this._buildingFonts = false;
  782.     if ( context.settingDefined( SETTINGS_CHANGE_SPAN_TO_FONT ) )
  783.         this._buildingFonts = true;
  784.  
  785.     // Get the HTML
  786.  
  787.     var html = context.getClipText();
  788.  
  789.     // Use ourselves to scan
  790.  
  791.     html = this.scan( html );
  792.  
  793.     // Set the HTML
  794.  
  795.     context.setClipText( html );
  796.  
  797.     context.debugInformation( "DecomposeClasses", "<< run" );
  798.  
  799.     return true;
  800. }
  801.  
  802. function DecomposeClasses_getPhase() { return PHASE_CONFORM_OTHER; }
  803.  
  804. function DecomposeClasses_parseClass( classDef, tagStruct )
  805. {
  806.     // Handle the different CSS style elements and stuff them into the
  807.     // right tagStruct item.
  808.  
  809.     if ( classDef[ "font-size" ] )
  810.         tagStruct.fontSize = classDef[ "font-size" ];
  811.  
  812.     if ( classDef[ "font-family" ] )
  813.         tagStruct.fontName = Utils_MapFont( classDef[ "font-family" ] );
  814.  
  815.     if ( classDef[ "font-style" ] == "italic" )
  816.         tagStruct.isItalic = true;
  817.  
  818.     if ( classDef[ "mso-break-type" ] == "section-break" )
  819.         tagStruct.isSectionBreak = true;
  820.  
  821.     if ( classDef[ "font-weight" ] > 400 )
  822.         tagStruct.isBold = true;
  823.  
  824.     if ( classDef[ "font-weight" ] == "bold" )
  825.         tagStruct.isBold = true;
  826.  
  827.     if ( classDef[ "text-decoration" ] == "underline" )
  828.         tagStruct.isUnderline = true;
  829.  
  830.     if ( classDef[ "text-align" ] == "right" || classDef[ "text-align" ] == "center" )
  831.         tagStruct.textAlign = classDef[ "text-align" ];
  832.  
  833.     // For indents we give one indent for every half inch of margin
  834.  
  835.     if ( classDef[ "mso-tab-count" ] != null )
  836.         tagStruct.tabCount = classDef[ "mso-tab-count" ];
  837.  
  838.     if ( classDef[ "margin-left" ] != null )
  839.     {
  840.         var value = classDef[ "margin-left" ];
  841.  
  842.         // grab numerical portion
  843.         var floatVal = parseFloat (value);
  844.          
  845.         // test if the format is in pica not inches
  846.         if ( value.match( /pt$/ ) )
  847.         {
  848.             // convert to inches
  849.             floatVal = floatVal * 0.16;
  850.         }
  851.         tagStruct.indent = Math.floor( floatVal * 0.5 );
  852.     }
  853.  
  854.     // Parse the colors, but don't allow vendor specific colors.
  855.  
  856.     if ( classDef[ "color" ] != null )
  857.     {
  858.         if( ! classDef[ "color" ].match( /windowtext/ig ) )
  859.             tagStruct.textColor = classDef[ "color" ];
  860.     }
  861.  
  862.     if ( classDef[ "background" ] != null )
  863.     {
  864.         if( ! classDef[ "background" ].match( /windowtext/ig ) )
  865.             tagStruct.bgColor = classDef[ "background" ];
  866.     }
  867. }
  868.  
  869. function DecomposeClasses_buildNewTags( tag, attributes, tagStruct )
  870. {
  871.     // The various attributes of the font/span tag
  872.  
  873.     var faceAttribute = "";
  874.     var styleAttribute = "";
  875.     var sizeAttribute = "";
  876.  
  877.     // Setup the FACE, STYLE and SIZE attributes of the font/span
  878.     // tag depending on the fontName, fontSize, and textColor attributes
  879.     // in the tagStruct.
  880.  
  881.     if ( tagStruct.fontName.length > 0 )
  882.     {
  883.         if ( this._buildingFonts )
  884.             faceAttribute = tagStruct.fontName;
  885.         else 
  886.         {
  887.             //Japanese windows system font needs to be wrapped around with quotes. Otherwise, Ringo and DW don't render the font correctly.
  888.             //First, find out if the font is a Japanese system font...
  889.             var isJapaneseFont = false;
  890.             for ( var i = 0; i < JAPANESE_SYSTEM_FONTS_WIN_LIST.length; i++ ) {
  891.                 if ( tagStruct.fontName.toLowerCase() == JAPANESE_SYSTEM_FONTS_WIN_LIST[i].toLowerCase() )
  892.                 {
  893.                     isJapaneseFont = true;
  894.                     i = JAPANESE_SYSTEM_FONTS_WIN_LIST.length;
  895.                 }
  896.             }
  897.             if (isJapaneseFont)
  898.                 styleAttribute += "font-family:" + "'" + tagStruct.fontName + "'" + ";";
  899.             else
  900.                 styleAttribute += "font-family:" + tagStruct.fontName + ";";
  901.  
  902.         }
  903.     }
  904.     if ( tagStruct.fontSize != null )
  905.     {
  906.         if ( this._buildingFonts )
  907.             sizeAttribute += Utils_ConvertPointsToFontSizes( tagStruct.fontSize );
  908.         else
  909.             styleAttribute += "font-size:" + tagStruct.fontSize + ";";
  910.     }
  911.     if ( tagStruct.textColor != null )
  912.         styleAttribute += "color:" + tagStruct.textColor + ";";
  913.  
  914.     // Initialize the starting and ending tag
  915.  
  916.     var startTag = "";
  917.     var endTag = "";
  918.     var replaceTag = false;
  919.  
  920.     // Indent the paragraph
  921.  
  922.     if ( tagStruct.indent > 0 && tag == "p" )
  923.     {
  924.         for( var indent = 0; indent < tagStruct.indent; indent++ )
  925.         {
  926.             startTag += "<blockquote>"; endTag = "</blockquote>" + endTag;
  927.         }
  928.         replaceTag = true;
  929.     }
  930.  
  931.     // Build the font/span tag
  932.  
  933.     if ( faceAttribute.length > 0 || styleAttribute.length > 0 || sizeAttribute.length > 0 )
  934.     {
  935.         var baseTagType = "font";
  936.         if ( !this._buildingFonts )
  937.             baseTagType = "span";
  938.  
  939.         startTag += "<" + baseTagType;
  940.     
  941.         if ( faceAttribute.length > 0 )
  942.             startTag += " face=\"" + faceAttribute + "\"";
  943.  
  944.         if ( sizeAttribute.length > 0 )
  945.             startTag += " size=\"" + sizeAttribute + "\"";
  946.  
  947.         if ( styleAttribute.length > 0 )
  948.             startTag += " style=\"" + styleAttribute + "\"";
  949.     
  950.         startTag += ">";
  951.     
  952.         endTag = "</" + baseTagType + ">" + endTag;
  953.     }
  954.  
  955.     // Add in italics and bolding
  956.  
  957.     if ( tagStruct.isItalic ) { startTag += "<i>"; endTag = "</i>" + endTag; }
  958.     if ( tagStruct.isBold ) { startTag += "<b>"; endTag = "</b>" + endTag; }
  959.     if ( tagStruct.isUnderline ) { startTag += "<u>"; endTag = "</u>" + endTag; }
  960.  
  961.     // Add some space for a section break
  962.  
  963.     if ( tagStruct.isSectionBreak )
  964.     {
  965.         attributes[ "style" ] = "";
  966.         startTag = "<br><br><br>" + startTag;
  967.     }
  968.  
  969.     // Fix bad anchor tags
  970.  
  971.     if ( tag == "a" && attributes[ "name" ] && attributes[ "name" ].match( /^_Toc/ ) )
  972.     {
  973.         attributes[ "name" ] = null;
  974.     }
  975.  
  976.     // Add in any tabs
  977.  
  978.     if ( tagStruct.tabCount != null )
  979.         for( var tab = 0; tab < parseInt( tagStruct.tabCount ); tab++ )
  980.             startTag += "  ";
  981.  
  982.     // If this is a span tag then we are replacing it because we are
  983.     // creating the font/span tag.
  984.  
  985.     if ( tag == "span" )
  986.         replaceTag = true;
  987.  
  988.     // Check for text alignment
  989.     if ( ( tag == "td" || tag.match( /^h[123456]/ ) ) && tagStruct.textAlign != null )
  990.     {
  991.         attributes[ "align" ] = tagStruct.textAlign;
  992.     }
  993.  
  994.     // Turn TDs into THs if this TD is in the THEAD section
  995.  
  996.     if ( tag == "td" && this._isHeader )
  997.     {
  998.         // We need to copy the attributes
  999.  
  1000.         var attributeStr = "";
  1001.  
  1002.         for ( var key in attributes )
  1003.         {
  1004.             if ( attributes[ key ] )
  1005.                 attributeStr += " " + key + "=\"" + attributes[ key ] + "\"";
  1006.         }
  1007.  
  1008.         // BGCOLOR is special because it doesn't exist yet, so we need to add it
  1009.  
  1010.         if ( tagStruct.bgColor != null )
  1011.             attributeStr += " bgcolor=\"" + tagStruct.bgColor + "\"";
  1012.  
  1013.         // This is hard wired for accesibility.  No Word version that I know of allows you
  1014.         // to turn columns into headers.  So we are assuming that rows are always the header
  1015.         // and thus the scope of the header is the column.
  1016.         //
  1017.         // This assumption should be checked against new versions of Word as they are released.
  1018.     
  1019.         attributeStr += ' scope="col"';
  1020.  
  1021.         // Put together the new TH tag
  1022.  
  1023.         startTag = "<th" + attributeStr + ">" + startTag;
  1024.         endTag = "</th>" + endTag;
  1025.         replaceTag = true;
  1026.     }
  1027.  
  1028.     // Return the fixup structure if we are fixing something up
  1029.  
  1030.     return { tag: tag, prefix: startTag, postfix: endTag, replace: replaceTag };
  1031. }
  1032.  
  1033. function DecomposeClasses_buildTagSpecifications( tag, attributes, tagStruct )
  1034. {
  1035.     // It takes a while to figure out just what font, italic, etc. combination maps to
  1036.     // any combination of tag name, class and style attributes.  On the assumption that
  1037.     // most people use the same combination over and over we cache the result of the
  1038.     // combination of tag name, class name and style text.
  1039.  
  1040.  
  1041.     // Create the cache names for the class and style.  Since it's a lookup we need
  1042.     // to actually have a value, so we replace null with an empty string
  1043.  
  1044.     var cacheClassName = new String( attributes[ 'class' ] );
  1045.     if ( cacheClassName == null )
  1046.         cacheClassName = "";
  1047.  
  1048.     var cacheStyleName = new String( attributes[ 'style' ] );
  1049.     if ( cacheStyleName == null )
  1050.         cacheStyleName = "";
  1051.  
  1052.     // Make sure that the cache hierachy exists for teh tag and the class name
  1053.  
  1054.     if ( this._cache[ tag ] == null )
  1055.         this._cache[ tag ] = {};
  1056.     if ( this._cache[ tag ][ cacheClassName ] == null )
  1057.         this._cache[ tag ][ cacheClassName ] = {}
  1058.  
  1059.     // Get value of the cache for this combination of tag, clas name and style
  1060.     // name
  1061.  
  1062.     var cacheValue = this._cache[ tag ][ cacheClassName ][ cacheStyleName ];
  1063.     if ( cacheValue )
  1064.     {
  1065.         attributes[ 'class' ] = null;
  1066.         for( var key in cacheValue )
  1067.             tagStruct[ key ] = cacheValue[ key ];
  1068.         return;
  1069.     }
  1070.  
  1071.     // Here we are populating the tagStruct from the CSS stuff. This is really in three sections;
  1072.     // first we look at the class for the tag.  Then we look at the class specified by the this tag
  1073.     // in particular. Then we look at the style data.  We do it in that order because that is the 
  1074.     // order in which CSS cascades.  If you change the ordering then you will be altering the
  1075.     // cascading behaviour of CSS.
  1076.  
  1077.  
  1078.     // First look to see if this tag has an associated class.  For example, if this
  1079.     // is a TD tag then this looks for a TD class.
  1080.  
  1081.     if ( this._context.getOriginClasses().get( tag ) )
  1082.         this.parseClass( this._context.getOriginClasses().get( tag ), tagStruct );
  1083.  
  1084.     // Now we look for the specific CLASS specified in the attribute, maybe
  1085.  
  1086.     var tagClassName = attributes[ 'class' ];
  1087.     if ( tagClassName )
  1088.     {
  1089.         var classDef = null;
  1090.  
  1091.         // First look for <tagName>.<className>
  1092.  
  1093.         var className = tag.toLowerCase() + "." + tagClassName;
  1094.         if ( this._context.getOriginClasses().get( className ) )
  1095.             classDef = this._context.getOriginClasses().get( className );
  1096.  
  1097.         // Then look for .<className>
  1098.  
  1099.         if ( classDef == null )
  1100.         {
  1101.             className = "." + tagClassName;
  1102.             if ( this._context.getOriginClasses().get( className ) )
  1103.                 classDef = this._context.getOriginClasses().get( className );
  1104.         }
  1105.  
  1106.         // If we find it then parse it
  1107.  
  1108.         if ( classDef )
  1109.             this.parseClass( classDef, tagStruct );
  1110.  
  1111.         attributes[ 'class' ] = null;
  1112.     }
  1113.  
  1114.     // Last, bring in the STYLE data
  1115.  
  1116.     if ( attributes.style )
  1117.     {
  1118.         this.parseClass( Utils_ParseStyle( attributes.style ), tagStruct );
  1119.     }
  1120.  
  1121.     var cacheValue = {};
  1122.     for( var key in tagStruct )
  1123.         cacheValue[ key ] = tagStruct[ key ];
  1124.     this._cache[ tag ][ cacheClassName ][ cacheStyleName ] = cacheValue;
  1125. }
  1126.  
  1127. function DecomposeClasses_shouldUseTargetClass( tag, attributes )
  1128. {
  1129.     // If we aren't allowing CSS then don't allow the user to reuse target
  1130.     // document classes
  1131.     
  1132.     if ( this._context.settingDefined( SETTINGS_NO_CSS ) )
  1133.         return false;
  1134.  
  1135.     // Check to see if we have a class that is defined by the target document.
  1136.     
  1137.     // JDH This code probably needs to get more complex to check the '<tagName>.<className>'
  1138.     // case and the '.<className>' case.
  1139.  
  1140.     if ( attributes[ 'class' ] )
  1141.         return this._context.getTargetClasses().has( attributes[ 'class' ] );
  1142.  
  1143.     return false;
  1144. }
  1145.  
  1146. function DecomposeClasses_startTag( tag )
  1147. {
  1148.     // Mark if we are in a header
  1149.  
  1150.     if ( tag.tag == "thead" )
  1151.         this._isHeader = true;
  1152. }
  1153.  
  1154. function DecomposeClasses_endTag( tag )
  1155. {
  1156.     // Mark when we leave a table header
  1157.  
  1158.     if ( tag.tag == "thead" )
  1159.         this._isHeader = false;
  1160. }
  1161.  
  1162. function DecomposeClasses_createTag( tag, attributes, closed )
  1163. {
  1164.     // Ignore this tag if it is in the ignore list
  1165.  
  1166.     if ( DC_IGNORE_TAGS[ tag ] > 0 )
  1167.         return null;
  1168.  
  1169.     if ( ! this.shouldUseTargetClass( tag, attributes ) )
  1170.     {
  1171.  
  1172.         // This is the finalized structure that should be populated after the class
  1173.         // and style attributes are analyzed.
  1174.  
  1175.         var tagStruct = {
  1176.             fontName: "",
  1177.             isItalic: false,
  1178.             isSectionBreak: false,
  1179.             isBold: false,
  1180.             tabCount: null,
  1181.             isUnderline: false,
  1182.             textAlign: null,
  1183.             fontSize: null,
  1184.             indent: null,
  1185.             textColor: null,
  1186.             bgColor: null
  1187.         };
  1188.  
  1189.         // Analyze the tag and populate the tag specifications structures
  1190.  
  1191.         this.buildTagSpecifications( tag, attributes, tagStruct );
  1192.  
  1193.         // Build a set of tags that represent the specification structure
  1194.  
  1195.         outStruct = this.buildNewTags( tag, attributes, tagStruct );
  1196.  
  1197.         // If we aren't replacing the current tag then look for any alterations
  1198.         // to the tag itself.
  1199.  
  1200.         var prefix = "";
  1201.         var postfix = "";
  1202.  
  1203.         // So there are two ways to handle a tag, we either add onto it, or replace it.
  1204.         // For example, here is an add to:
  1205.         //
  1206.         //    <p class=MsoNormal>text</p>
  1207.         //
  1208.         // Becomes:
  1209.         //
  1210.         //    <p><font face="Times New Roman">text</font></p>
  1211.         //
  1212.         // Or replacing, like:
  1213.         //
  1214.         //    <p style="mso-indent-level:2>text</p>
  1215.         //
  1216.         // Is replaced by:
  1217.         //
  1218.         //    <blockquote><blockquote>text</blockquote></blockquote>
  1219.         //
  1220.         // Another case is replacing <span> tags with <font> tags.
  1221.  
  1222.         if ( outStruct.replace )
  1223.         {
  1224.             prefix = outStruct.prefix;
  1225.             postfix = outStruct.postfix;
  1226.         }
  1227.         else
  1228.         {
  1229.             if ( tagStruct.bgColor != null )
  1230.                 attributes.bgcolor = tagStruct.bgColor;
  1231.  
  1232.             var retVal = StructureScanner_createTag( tag, attributes, closed );
  1233.  
  1234.             prefix = retVal.prefix + outStruct.prefix;
  1235.             postfix = outStruct.postfix + retVal.postfix;
  1236.         }
  1237.  
  1238.         return { prefix: prefix, postfix: postfix };
  1239.     }
  1240.  
  1241.     return null;
  1242. }
  1243.  
  1244.  
  1245.  
  1246. //---------------------------------------------------------------------------------------------------------
  1247. // DemoteToParagraphs
  1248. //---------------------------------------------------------------------------------------------------------
  1249.  
  1250. // DemoteToParagraphs turns and <H1>, <H2>, <H3>, etc. tag into a 
  1251. // <P> tag if the SETTINGS_DEMOTE_TO_PARAGRAPHS setting is on.
  1252.  
  1253. function DemoteToParagraphs() { }
  1254.  
  1255. // The module API
  1256.  
  1257. DemoteToParagraphs.prototype.run = DemoteToParagraphs_run;
  1258. DemoteToParagraphs.prototype.getPhase = DemoteToParagraphs_getPhase;
  1259.  
  1260. function DemoteToParagraphs_run( context )
  1261. {
  1262.     // Ignore non-HTML content
  1263.  
  1264.     if ( context.getContentType() != CONTENT_TYPE_HTML )
  1265.         return true;
  1266.  
  1267.     // Ignore this filter if we are not in Contribute
  1268.  
  1269.     if ( ! context.settingDefined( SETTINGS_CONTRIBUTE ) )
  1270.         return true;
  1271.  
  1272.     // Ignore this filter if we are not in demoting to paragraphs
  1273.  
  1274.     if ( ! context.settingDefined( SETTINGS_DEMOTE_TO_PARAGRAPHS ) )
  1275.         return true;
  1276.  
  1277.     context.debugInformation( "DemoteToParagraphs", ">> run" );
  1278.  
  1279.     // Get the HTML
  1280.  
  1281.     var html = context.getClipText();
  1282.  
  1283.     // Put together the regular expression of what to find
  1284.  
  1285.     var mapRE = /^h[123456]$/i;
  1286.  
  1287.     // And then send it in with what we want it replaced with
  1288.  
  1289.     var tagNameMapper = new MapTagNamesScanner( mapRE, "p" );
  1290.  
  1291.     // Run the scanner
  1292.  
  1293.     html = tagNameMapper.scan( html, context );
  1294.  
  1295.     // Replace the HTML
  1296.  
  1297.     context.setClipText( html );
  1298.  
  1299.     context.debugInformation( "DemoteToParagraphs", "<< run" );
  1300.  
  1301.     return true;
  1302. }
  1303.  
  1304. function DemoteToParagraphs_getPhase() { return PHASE_FIXUP; }
  1305.  
  1306.  
  1307.  
  1308. //---------------------------------------------------------------------------------------------------------
  1309. // SingleSpaceParagraphs
  1310. //---------------------------------------------------------------------------------------------------------
  1311.  
  1312. // If the SETTINGS_SINGLE_SPACE_P setting is on then SingleSpaceParagraphs adds
  1313. // the margin-top:0 and margin-bottom:0 style attributes to each paragraph.
  1314.  
  1315. function SingleSpaceParagraphs() { }
  1316.  
  1317. // The module API
  1318.  
  1319. SingleSpaceParagraphs.prototype.run = SingleSpaceParagraphs_run;
  1320. SingleSpaceParagraphs.prototype.getPhase = SingleSpaceParagraphs_getPhase;
  1321.  
  1322. function SingleSpaceParagraphs_run( context )
  1323. {
  1324.     // Ignore non-HTML content
  1325.  
  1326.     if ( context.getContentType() != CONTENT_TYPE_HTML )
  1327.         return true;
  1328.  
  1329.     // Ignore this filter if we are not in Contribute
  1330.  
  1331.     if ( ! context.settingDefined( SETTINGS_CONTRIBUTE ) )
  1332.         return true;
  1333.  
  1334.     // Ignore this filter if we are not single spacing paragraphs
  1335.  
  1336.     if ( ! context.settingDefined( SETTINGS_SINGLE_SPACE_P ) )
  1337.         return true;
  1338.  
  1339.     context.debugInformation( "SingleSpaceParagraphs", ">> run" );
  1340.  
  1341.  
  1342.     var html = context.getClipText();
  1343.  
  1344.     var styleAdder = new AddStylesScanner( "p", { 'margin-top': 0, 'margin-bottom': 0 } );
  1345.     html = styleAdder.scan( html, context );
  1346.  
  1347.     context.setClipText( html );
  1348.  
  1349.  
  1350.     context.debugInformation( "SingleSpaceParagraphs", "<< run" );
  1351.  
  1352.     return true;
  1353. }
  1354.  
  1355. function SingleSpaceParagraphs_getPhase() { return PHASE_FINALIZE; }
  1356.  
  1357.  
  1358. //---------------------------------------------------------------------------------------------------------
  1359. // MergeRedundantFontTags
  1360. //---------------------------------------------------------------------------------------------------------
  1361.  
  1362. // This handler fixes a problem where the system creates font tags within font tags.  For
  1363. // example:
  1364. //
  1365. //     <font name="Times New Roman" size="7"><b><font size="3">Example</font></b></font>
  1366. //
  1367. // Should become:
  1368. //
  1369. //     <font name="Times New Roman" size="3"><b>Example</b></font>
  1370. //
  1371. // What actually happens is that the tags become:
  1372. //
  1373. //     <font name="Times New Roman" size="7"><b><font>Example</font></b></font>
  1374. //
  1375. // And the empty font tags are removed in the finalization process.
  1376.  
  1377. function MergeRedundantFontTags() {}
  1378.  
  1379. MergeRedundantFontTags.prototype = new StructureScanner ();
  1380.  
  1381. // The module API
  1382.  
  1383. MergeRedundantFontTags.prototype.run = MergeRedundantFontTags_run;
  1384. MergeRedundantFontTags.prototype.getPhase = MergeRedundantFontTags_getPhase;
  1385.  
  1386. // The structure scanner override methods
  1387.  
  1388. MergeRedundantFontTags.prototype.inspectTag = MergeRedundantFontTags_inspectTag;
  1389. MergeRedundantFontTags.prototype.findFontTag = MergeRedundantFontTags_findFontTag;
  1390.  
  1391. function MergeRedundantFontTags_run( context )
  1392. {
  1393.     // Ignore non-HTML content
  1394.  
  1395.     if ( context.getContentType() != CONTENT_TYPE_HTML )
  1396.         return true;
  1397.  
  1398.     // Ignore this filter if we are not in Contribute
  1399.  
  1400.     if ( ! context.settingDefined( SETTINGS_CONTRIBUTE ) )
  1401.         return true;
  1402.  
  1403.     context.debugInformation( "MergeRedundantFontTags", ">> run" );
  1404.  
  1405.     // Get the HTML
  1406.  
  1407.     var html = context.getClipText();
  1408.  
  1409.     // Use ourselves to scan the HTML
  1410.  
  1411.     html = this.scan( html );
  1412.  
  1413.     // Replace the HTML
  1414.  
  1415.     context.setClipText( html );
  1416.  
  1417.     context.debugInformation( "MergeRedundantFontTags", "<< run" );
  1418.  
  1419.     return true;
  1420. }
  1421.  
  1422. function MergeRedundantFontTags_getPhase() { return PHASE_OPTIMIZE; }
  1423.  
  1424. function MergeRedundantFontTags_findFontTag( tag )
  1425. {
  1426.     // We ignore interior text
  1427.  
  1428.     if ( tag.type == "text" )
  1429.         return null;
  1430.  
  1431.     // We check to see if there are children, there must be only one child
  1432.     // at each level
  1433.  
  1434.     if ( tag.children.length == 1 )
  1435.     {
  1436.         // If the child is a font tag, then we have a qualifying tag/subTag
  1437.         // combination.  Otherwise we delve further.
  1438.  
  1439.         if ( tag.children[ 0 ].tag == "font" )
  1440.             return tag.children[ 0 ];
  1441.         else
  1442.             return this.findFontTag( tag.children[ 0 ] );
  1443.     }
  1444.  
  1445.     return null;
  1446. }
  1447.  
  1448. function MergeRedundantFontTags_inspectTag( tag )
  1449. {
  1450.     // In the inspection phase of the StructureScanner process we can actually alter the
  1451.     // tag before it's output.  In this case we want to take interior font tags and merge
  1452.     // them into the parent font tag.
  1453.  
  1454.     // Just to make things more clear, in the example above:
  1455.     //
  1456.     //     <font name="Times New Roman" size="7"><b><font size="3">Example</font></b></font>
  1457.     //
  1458.     // At the point we find them:
  1459.     //
  1460.     // 'tag' = <font name="Times New Roman" size="7">
  1461.     // 'subTag' = <font size="3">
  1462.     //
  1463.     // Then after we filter it:
  1464.     //
  1465.     // 'tag' = <font name="Times New Roman" size="3">
  1466.     // 'subTag' = <font>
  1467.     //
  1468.  
  1469.  
  1470.     if ( tag.tag == "font" && tag.children.length > 0 )
  1471.     {
  1472.         // Find a qualifying interior font tag
  1473.  
  1474.         var subTag = this.findFontTag( tag );
  1475.  
  1476.         // If we found one then migrate all of the attributes from that tag into the parent
  1477.         // tag and mark them as null in the sub tag.
  1478.  
  1479.         if ( subTag )
  1480.         {
  1481.             for ( var key in subTag.attributes )
  1482.             {
  1483.                 tag.attributes[ key ] = subTag.attributes[ key ];
  1484.                 subTag.attributes[ key ] = null;
  1485.             }
  1486.         }
  1487.     }
  1488.  
  1489.     return tag;
  1490. }
  1491.  
  1492.  
  1493. //---------------------------------------------------------------------------------------------------------
  1494. // ChangeToStrongAndEm
  1495. //---------------------------------------------------------------------------------------------------------
  1496.  
  1497. // ChangeToStrongAndEm changes <b> tags to <strong> tags, and <i> tags to <em>
  1498. // tags if the SETTINGS_USE_EMPHASIS setting is on.
  1499.  
  1500. function ChangeToStrongAndEm() { }
  1501.  
  1502. // The module API
  1503.  
  1504. ChangeToStrongAndEm.prototype.run = ChangeToStrongAndEm_run;
  1505. ChangeToStrongAndEm.prototype.getPhase = ChangeToStrongAndEm_getPhase;
  1506.  
  1507. function ChangeToStrongAndEm_run( context )
  1508. {
  1509.     // Ignore non-HTML content
  1510.  
  1511.     if ( context.getContentType() != CONTENT_TYPE_HTML )
  1512.         return true;
  1513.  
  1514.     // Ignore this filter if we are not in Contribute
  1515.  
  1516.     if ( ! context.settingDefined( SETTINGS_CONTRIBUTE ) )
  1517.         return true;
  1518.  
  1519.     // Ignore this filter if we are not changing to <strong> and <em>
  1520.  
  1521.     if ( ! context.settingDefined( SETTINGS_USE_EMPHASIS ) )
  1522.         return true;
  1523.  
  1524.     context.debugInformation( "ChangeToStrongAndEm", ">> run" );
  1525.  
  1526.     // Get the HTML
  1527.  
  1528.     var html = context.getClipText();
  1529.  
  1530.     // Setup regexps for <b> and <i> and turn them into <strong>
  1531.     // and <em>.
  1532.  
  1533.     var mapB = /^b$/i;
  1534.     var tagNameMapperB = new MapTagNamesScanner( mapB, "strong" );
  1535.     html = tagNameMapperB.scan( html, context );
  1536.  
  1537.     var mapI = /^i$/i;
  1538.     var tagNameMapperI = new MapTagNamesScanner( mapI, "em" );
  1539.     html = tagNameMapperI.scan( html, context );
  1540.  
  1541.     // Replace the HTML
  1542.  
  1543.     context.setClipText( html );
  1544.  
  1545.     context.debugInformation( "ChangeToStrongAndEm", "<< run" );
  1546.  
  1547.     return true;
  1548. }
  1549.  
  1550. function ChangeToStrongAndEm_getPhase() { return PHASE_FINALIZE; }
  1551.  
  1552.  
  1553.  
  1554.